/*
 *  Copright: 2008 Philipp Leitner, Distributed Systems Group,
 *            TU Wien, leitner@infosys.tuwien.ac.at
 *
 *  This file is part of DAIOS.
 *
 *  DAIOS is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation.
 * 
 *  DAIOS is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with DAIOS.  If not, see
 *  <http://www.gnu.org/licenses/>.
 * 
 */

package at.ac.tuwien.infosys.dsg.daios.wsdl.matcher;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import at.ac.tuwien.infosys.dsg.daios.exceptions.AmbigiousException;
import at.ac.tuwien.infosys.dsg.daios.exceptions.NoArrayException;
import at.ac.tuwien.infosys.dsg.daios.exceptions.TypeErrorException;
import at.ac.tuwien.infosys.dsg.daios.framework.structs.DaiosInputMessage;
import at.ac.tuwien.infosys.dsg.daios.framework.structs.DaiosMessage;
import at.ac.tuwien.infosys.dsg.daios.wsdl.AtomicTypesMapper;
import at.ac.tuwien.infosys.dsg.daios.wsdl.impl.WSDLArrayType;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLPart;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLType;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLMessage;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLOperation;
import at.ac.tuwien.infosys.dsg.daios.wsdl.typesystem.WSDLTypeTree;

/**
 * The concrete Matcher which is responsible for doc/lit style matching.
 * 
 * @author philipp
 *
 */
public class DocumentLiteralMatcher extends Matcher {
	
	/**
	 * Calculate the distance between a single WSDL message and a
	 * Daios input message.
	 * 
	 * @param message The Daios input message.
	 * @param msgToUse The WSDL message to compare to the Daios message.
	 * @param operations A list of all operations to consider.
	 * @param types A type tree containing all types from the WSDL definition.
	 * @return The distance between the Daios message and the WSDL message as Integer.
	 * 
	 * @throws AmbigiousException
	 */
	protected int distance(DaiosInputMessage message, IWSDLMessage msgToUse,
			List<IWSDLOperation> operations, WSDLTypeTree types)
		throws AmbigiousException {

		// return MAX if message is no input message
		if(!isInputMessage(msgToUse, operations))
			return MAX_DISTANCE;
		
		// the basic algorithm here is the same as in the RPC matcher
		// but the  single matching methods are implemented
		// differently
		
		List<IWSDLPart> parts = msgToUse.getParts();
		Hashtable<String,String> partsList = new Hashtable<String,String>();
		for(IWSDLPart p:parts) {
			partsList.put(p.getPartName(),p.getPartType());
		}
		
		Enumeration<String> names = message.getNames();
		String n = null;
		Class t = null;
		while(names.hasMoreElements()) {
			n = names.nextElement();
			t = message.get(n).getClass();
			
			// value does not exist
			if(partsList.get(n) == null)
				return MAX_DISTANCE;
			
			// get this type
			IWSDLType partType =
				types.getGlobalElement(partsList.get(n));
			
			boolean isSimple = message.isSimple(n);
			boolean isArray = message.isArray(n);
			
			// simple type
			if(isSimple && !isArray) {
				
				if(!simpleMatch(partType, t))
					return MAX_DISTANCE;
			
			}
			
			// array type
			else if(isSimple && isArray) {
				
				if(!arrayMatch(partType, message.get(n).getClass())) {
					return MAX_DISTANCE;
				}
				
			}
			
			// complex type
			else if(!isSimple && !isArray) {
				try {
					if(!complexMatch(partType, message.getComplex(n)))
						return MAX_DISTANCE;
				} catch (TypeErrorException e) {
					e.printStackTrace();
					return MAX_DISTANCE;
				}
			}
			
			// array of complex values
			else {
				try {
					if(!complexArrayMatch(partType, message.getComplexArray(n)))
						return MAX_DISTANCE;
				} catch (TypeErrorException e) {
					e.printStackTrace();
					return MAX_DISTANCE;
				}
			}
					
			partsList.remove(n);
		}
		
		return partsList.size();
	}

	/**
	 * Check if this Daios simple type matches a 
	 * given WSDL type.
	 * 
	 * @param partType The WSDL type.
	 * @param t The Daios simple type.
	 * @return True if the WSDL and the Daios type match,
	 * false otherwise.
	 */
	private boolean simpleMatch(IWSDLType partType, Class t) {
		return t.equals(
			AtomicTypesMapper.get(partType.getTypeName().getLocalPart()));
	}
	
	/**
	 * Check if this Daios array type matches a 
	 * given WSDL type.
	 * 
	 * @param partType The WSDL type.
	 * @param arrayclass The Daios array type.
	 * @return True if the WSDL and the Daios types match,
	 * false otherwise.
	 * 
	 * @throws NoArrayException
	 */
	private boolean arrayMatch(IWSDLType partType, Class arrayclass) {
		
		WSDLArrayType arrayPart = null;
		if(! (partType instanceof WSDLArrayType))
			return false;
		
		arrayPart = (WSDLArrayType) partType;
		
		return arrayclass.equals(
				AtomicTypesMapper.get(arrayPart.getArrayType()));
		
	}

	/**
	 * Check if this Daios complex type matches a 
	 * given WSDL type.
	 * 
	 * @param partType The WSDL type.
	 * @param complex The Daios complex type.
	 * @return True if the WSDL and the Daios types match,
	 * false otherwise.
	 */	
	private boolean complexMatch(IWSDLType partType, DaiosMessage complex) {
		return complex.structuralEqual(partType.getStructure());
	}
	
	/**
	 * Check if this array of Daios complex types matches a 
	 * given WSDL type.
	 * 
	 * @param partType The WSDL type.
	 * @param complexArray The Daios complex type array.
	 * @return True if the WSDL and the Daios array match,
	 * false otherwise.
	 */	
	private boolean complexArrayMatch(IWSDLType partType, DaiosMessage[] complexArray) {
		
		// we can assume that every element of complexArray has the same structure
		// (this should be enforced by the Daios platform)
		return complexMatch(partType, complexArray[0]);
		
	}
	
}
